package com.tianji.learning.handler;

import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.DateUtils;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.po.PointsRecord;
import com.tianji.learning.service.IPointsBoardSeasonService;
import com.tianji.learning.service.IPointsBoardService;
import com.tianji.learning.service.IPointsRecordService;
import com.tianji.learning.utils.TableInfoContext;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

import static com.tianji.learning.constants.TableConstants.POINTS_BOARD_TABLE_PREFIX;
import static com.tianji.learning.constants.TableConstants.POINTS_RECORD_TABLE_PREFIX;

/**
 * @author Covey
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class PointsBoardPersistentHandler {

    private final IPointsBoardSeasonService seasonService;

    private final IPointsBoardService pointsBoardService;

    private final StringRedisTemplate redisTemplate;

    private final IPointsRecordService recordService;

    /**
     * 创建数据库表，以便将redis中的赛季排行榜写入磁盘
     * Cron: 0 0 3 1 * ?
     * 每月1号凌晨3:00执行
     */
    @XxlJob("createTableJob")
    public void createPointsBoardTableOfLastSeason() {
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);
        // 2.查询赛季id
        Integer season = seasonService.querySeasonByTime(time);
        if (season == null) {
            // 赛季不存在
            return;
        }
        // 3.创建表
        pointsBoardService.createPointsBoardTableBySeason(season);
    }

    /**
     * 将redis中的赛季排行榜写入磁盘
     */
    @XxlJob("savePointsBoard2DB")
    public void savePointsBoard2Db() {
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);
        // 2.计算动态表名
        // 2.1.查询赛季信息
        Integer season = seasonService.querySeasonByTime(time);
        // 2.2.将表名存入ThreadLocal
        TableInfoContext.setInfo(POINTS_BOARD_TABLE_PREFIX + season);
        // 3.查询榜单数据
        // 3.1.拼接KEY
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + time.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        // 3.2.查询数据
        // 分片广播写入数据库，多个服务分别将一部分Redis数据写入数据库，以此提高性能
        // 分片参数
        // 当前分片索引（从0开始）
        int index = XxlJobHelper.getShardIndex();
        // 总分片数
        int total = XxlJobHelper.getShardTotal();
        int pageNo = index + 1;
        int pageSize = 10;
        while (true) {
            List<PointsBoard> boardList = pointsBoardService.queryCurrentBoardList(key, pageNo, pageSize);
            if (CollUtils.isEmpty(boardList)) {
                break;
            }
            // 4.持久化到数据库
            // 4.1.把排名信息写入id
            boardList.forEach(b -> {
                b.setId(b.getRank().longValue());
                b.setRank(null);
            });
            // 4.2.持久化
            pointsBoardService.saveBatch(boardList);
            // 5.翻页
            pageNo += total;
        }
        // 任务结束，移除动态表名
        TableInfoContext.remove();
    }

    /**
     * 清空Redis
     */
    @XxlJob("clearPointsBoardFromRedis")
    public void clearPointsBoardFromRedis() {
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);
        // 2.计算key
        String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + time.format(DateUtils.POINTS_BOARD_SUFFIX_FORMATTER);
        // 3.异步删除
        redisTemplate.unlink(key);
    }

    /**
     * 创建数据库表，以便实现将 每个历史赛季的积分明细从points_record表迁移到一张独立的表中
     * 表名称规则points_record_xx，这里的xx就是赛季id
     * 当前赛季的数据依然保存在points_record表不变
     * Cron: 0 0 4 1 * ?
     * 每月1号凌晨4:00执行
     */
    @XxlJob("createPointsRecordTableJob")
    public void createPointsRecordTableOfLastSeason() {
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);
        // 2.查询赛季id
        Integer season = seasonService.querySeasonByTime(time);
        if (season == null) {
            // 赛季不存在
            return;
        }
        // 3.创建表
        pointsBoardService.createPointsRecordTableBySeason(season);
    }

    /**
     * 将历史赛季的积分明细从points_record表迁移到一张独立的表中
     */
    @XxlJob("movePointsRecord2NewDb")
    public void movePointsRecord2NewDb() {
        // 获取分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        log.info("开始执行积分记录迁移[分片: {}/{}]", shardIndex + 1, shardTotal);
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);
        // 2.计算动态表名
        Integer season = seasonService.querySeasonByTime(time);
        String targetTableName = POINTS_RECORD_TABLE_PREFIX + season;
        // 3.查询上个赛季的数据库数据
        // 从源表(points_record)查询上赛季数据
        // 分片查询数据
        List<PointsRecord> pointsRecordList = recordService.selectBySeasonWithShard(season, shardIndex, shardTotal);
        if (CollUtils.isEmpty(pointsRecordList)) {
            log.info("赛季{}分片{}无积分记录需要迁移", season, shardIndex);
            return;
        }
        // 4.使用SQL批量插入到目标表
        boolean success = recordService.batchInsertToTargetTable(targetTableName, pointsRecordList);
        if (success) {
            log.info("成功迁移{}条积分记录到表{}", pointsRecordList.size(), targetTableName);
        } else {
            log.error("迁移积分记录到表{}失败", targetTableName);
        }
    }

    /**
     * 从points_record表中删除已被迁移的数据
     */
    @XxlJob("clearPointsRecordFromDb")
    public void clearPointsRecordFromDb() {
        // 1.获取上月时间
        LocalDateTime time = LocalDateTime.now().minusMonths(1);
        // 2.查询赛季信息
        Integer season = seasonService.querySeasonByTime(time);
        // 3.删除数据
        recordService.deleteBySeason(season);
    }
}
